package com.oliver.proxy.midjourney.application.service;

import com.oliver.proxy.midjourney.domain.entity.Prod;
import com.oliver.proxy.midjourney.domain.entity.TaskList;
import com.oliver.proxy.midjourney.infrastructure.mapper.ProdMapper;
import com.oliver.proxy.midjourney.infrastructure.mapper.TaskListMapper;
import com.oliver.proxy.midjourney.infrastructure.mapper.FinalProdMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SpuProcessingService {
    private final ProdMapper prodMapper;
    private final TaskListMapper taskListMapper;
    private final FinalProdMapper finalProdMapper;
    private final ExecutorService executor;
    private final AtomicBoolean isProcessing = new AtomicBoolean(false);
    private static final int THREAD_COUNT = 4;
    
    // Daily sequence counter map (reset daily)
    private final Map<String, AtomicInteger> categorySequenceMap = new ConcurrentHashMap<>();
    private LocalDate currentDate = LocalDate.now();

    @Autowired
    public SpuProcessingService(ProdMapper prodMapper, TaskListMapper taskListMapper, 
                              FinalProdMapper finalProdMapper) {
        this.prodMapper = prodMapper;
        this.taskListMapper = taskListMapper;
        this.finalProdMapper = finalProdMapper;
        this.executor = Executors.newFixedThreadPool(THREAD_COUNT);
        initializeDailyCounters();
    }

    private void initializeDailyCounters() {
        categorySequenceMap.clear();
        // Now using dynamic keys based on date, prototype number and category
        // No need to pre-initialize since we're using computeIfAbsent in the generateSpuCode method
    }

    @PostConstruct
    public void init() {
        CompletableFuture.runAsync(this::processSpuInBackground);
    }

    @Scheduled(fixedRate = 6000)
    public void checkAndRestartProcessing() {
        if (!isProcessing.get()) {
            log.info("Restarting SPU processing...");
            CompletableFuture.runAsync(this::processSpuInBackground);
        }
    }

    @Scheduled(cron = "0 0 0 * * ?")
    public void resetDailyCounters() {
        log.info("Resetting daily SPU sequence counters");
        // When the date changes, existing counters become irrelevant because the date is part of the key
        initializeDailyCounters();
        currentDate = LocalDate.now();
    }

    private void processSpuInBackground() {
        try {
            if (!isProcessing.compareAndSet(false, true)) {
                return;
            }
            
            log.info("Starting SPU processing in background thread");
            while (true) {
                List<Map<String, Object>> prodsData = prodMapper.selectProdsWithEmptyAttribute5();
                
                if (prodsData.isEmpty()) {
                    Thread.sleep(3000);
                    continue;
                }

                // Process prods
                processProds(prodsData);
                
                // Short sleep to prevent CPU overload
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            log.error("Error in SPU background processing", e);
        } finally {
            isProcessing.set(false);
        }
    }

    private void processProds(List<Map<String, Object>> prodsData) {
        // First, group by task_id
        Map<Long, List<Map<String, Object>>> taskGroups = prodsData.stream()
                .collect(Collectors.groupingBy(data -> Long.parseLong(data.get("task_id").toString())));

        taskGroups.forEach((taskId, prods) -> {
            // Get task category
            String taskCategory = prods.get(0).get("task_category").toString();
            
            // Get corresponding TaskList for the taskId
            TaskList taskList = taskListMapper.selectById(taskId);
            
            // Further group by prompt within each task
            Map<String, List<Map<String, Object>>> promptGroups = prods.stream()
                    .collect(Collectors.groupingBy(data -> data.get("prompt").toString()));

            promptGroups.forEach((prompt, promptProds) -> {
                if ("C".equals(taskCategory)) {
                    processCategory_C(promptProds, taskList);
                } else if ("M".equals(taskCategory)) {
                    processCategory_M(promptProds, taskList);
                }
            });
        });
    }

    private void processCategory_C(List<Map<String, Object>> prods, TaskList taskList) {
        // Process strictly in groups of 3
        List<List<Map<String, Object>>> batches = new ArrayList<>();
        for (int i = 0; i < prods.size(); i += 3) {
            // Only process complete groups of 3
            if (i + 2 < prods.size()) {
                batches.add(prods.subList(i, i + 3));
            }
        }

        // Process complete groups of 3
        for (List<Map<String, Object>> batch : batches) {
            String spuCode = generateSpuCode("C", taskList, batch.get(0).get("prompt").toString());
            updateProdsAttribute5(batch, spuCode);
        }
    }

    private void processCategory_M(List<Map<String, Object>> prods, TaskList taskList) {
        // Process one by one
        for (Map<String, Object> prod : prods) {
            String spuCode = generateSpuCode("M", taskList, prod.get("prompt").toString());
            // For category M, always process exactly one product
            updateProdsAttribute5(Collections.singletonList(prod), spuCode);
        }
    }

    private String generateSpuCode(String category, TaskList taskList, String prompt) {
        // Format: {category}{开发编号}-{样机编号}{日期}{编号}
        
        // Get the development number (attribute2)
        String devNumber = "00";
        if (taskList != null && taskList.getAttritube2() != null && !taskList.getAttritube2().isEmpty()) {
            try {
                // Format to 2 digits
                int devNum = Integer.parseInt(taskList.getAttritube2().trim());
                devNumber = String.format("%02d", devNum);
            } catch (NumberFormatException e) {
                // If can't parse as number, use "00"
                log.warn("Cannot parse development number from attribute2: {}", taskList.getAttritube2());
            }
        }
        
        // Get the prototype number (attribute1)
        String prototypeNumber = "";
        if (taskList != null && taskList.getAttritube1() != null && !taskList.getAttritube1().isEmpty()) {
            String[] parts = taskList.getAttritube1().split("-");
            if (parts.length > 0) {
                prototypeNumber = parts[parts.length - 1];
            }
        }
        
        // Format date as MMddyy
        String dateStr = currentDate.format(DateTimeFormatter.ofPattern("MMddyy"));
        
        // Get the maximum sequence number from the database
        String maxSeq = prodMapper.getMaxSequenceNumber(category, devNumber, prototypeNumber, dateStr);
        int sequence;
        if (maxSeq != null) {
            // Convert base36 to decimal, increment, then back to base36
            sequence = Integer.parseInt(maxSeq, 36) + 1;
        } else {
            sequence = 1;
        }
        
        // Convert to base 36 and format to 4 positions
        String base36Sequence = Integer.toString(sequence, 36).toUpperCase();
        while (base36Sequence.length() < 4) {
            base36Sequence = "0" + base36Sequence;
        }
        
        // Assemble the final code
        return String.format("%s%s-%s%s%s", 
                category, 
                devNumber,
                prototypeNumber, 
                dateStr, 
                base36Sequence);
    }

    @Transactional
    public void updateProdsAttribute5(List<Map<String, Object>> prods, String spuCode) {
        // Additional validation to ensure group size requirements
        if ((spuCode.startsWith("C") && prods.size() != 3) ||
            (spuCode.startsWith("M") && prods.size() != 1)) {
            return;
        }

        List<Long> prodIds = prods.stream()
                .map(prod -> Long.parseLong(prod.get("data_id").toString()))
                .collect(Collectors.toList());
        
        // Update prod table
        prodMapper.updateBatchAttribute5(prodIds, spuCode);
        
        // Insert into final_prod table
//        try {
//            finalProdMapper.batchInsert(Collections.singletonList(spuCode));
//            log.info("Updated attribute5 to {} for {} prods and inserted into final_prod",
//                    spuCode, prodIds.size());
//        } catch (Exception e) {
//            log.error("Error inserting into final_prod table for spuCode: {}", spuCode, e);
//            throw e; // 让事务回滚
//        }
    }
}